最难理解的地方

其实DAL最难理解的就是,函数里面嵌套函数,将函数抽象为参数传递到函数中,并且函数参数里面还有参数,这是我最陌生的地方。只要多接触,自然就熟悉了。

如果只是单纯的函数作为参数传递进来,我还是可以理解的:

但如果函数作为参数,并且函数参数里面定义有参数和返回值类型,我看上去就很难理解了:

其实仔细拆解一下,就会发现,operation就是一个函数,并且在调用operation函数的时候,可以接收到传递过来的session参数,就是这里难以理解。这就是闭包的概念。但是这里非常抽象,就是operation其实我不知道是什么样的,

data access layer(DAL) 代码拆解

参考webdevsimplied这个老师的做法。这个DAL是每个项目都必须的,必须把这个功能加上。

老师的项目使用了sqlite3,所以不需要专门的数据库,使用npm install,然后运行drizzle相关的命令即可。重点关注两个页面:/todos和/admin。

_naive_dal-basic_dal-advanced三种类型的文件,分别对应的是不同的data access layer写法,可以将其替换到page.tsx里面看效果。

image-20260121214809952

action函数也有data access layer,也是分为三种类型,可以对比看。

image-20260121215042463

初步分析

  1. naive写法

naive里面的代码就不要参考了,这是不推荐的写法,但是dal-advanced里面的封装太复杂了,这个暂时还看不懂,所以直接参考dal-basic的写法,能够写成这样就已经很不错了。dal-advanced里面的代码要慢慢学会写。

naive的写法就是在页面中直接进行用户权限检查和数据库查询,这样会导致大量的样板代码,如果直接在页面查询,很容易忘记添加权限检查,从而导致安全漏洞(例如普通用户访问了管理员数据)。反之将数据获取逻辑封装在函数中,可以更方便地统一应用 Next.js 的缓存机制(如 cacherevalidateTag)。

下面这样的代码就不要写了:

image-20260122143447290

  1. dal-basic写法

最起码要写成这样的,要先封装一个函数,然后在页面中调用(也就相当于把检查用户是否登录的语句放到封装函数里面了):

image-20260428142334417

image-20260122143644575

 

仔细了解一下之后,好像dal-basic也太简单了一点,dal-basic的mutations和queries里面对user的校验代码也很重复,而且还是手动抛出错误,但就是简单,别人一看代码就明白了。

  1. dal-advanced写法

dal-advanced的代码封装,太抽象了一点,我看了很久,很难总结出它到底做了什么,我问一下AI:

在视频中,Kyle 介绍的 dal-advanced(高级数据访问层)模式,核心思想是将数据获取逻辑从“直接抛出错误/重定向”转变为“返回结构化的结果对象”

这种模式模仿了类似 Rust 的 Result 类型或验证库(如 Zod)的返回结构。以下是该模式代码设计的详细拆解:

  1. 核心数据结构:Result 对象

dal-advanced 中,DAL 函数不再直接返回数据,而是返回一个包含状态的对象。其 TypeScript 定义大致如下:

为什么要这么做?

  1. 标准化的 DAL 函数写法

一个高级 DAL 函数(如获取用户待办事项)的代码结构通常如下:

  1. 调用层的灵活处理(分层消费)

这是 dal-advanced 最强大的地方,不同的调用方可以根据返回的 error 做出不同反应:

场景 A:在 React Server Component (页面) 中

如果你希望用户没登录就直接跳到登录页,可以使用一个辅助函数 verifyData

场景 B:在 API Route 或 Server Action 中

如果你正在处理一个异步请求,不希望页面跳转,而是想给前端一个 JSON 错误:

  1. 进阶:使用 Wrapper 减少冗余

视频中还提到了创建一个通用的包装器(如 dbWrapper),自动处理数据库错误捕获:

总结 dal-advanced 的代码优势:

  1. 强类型支持:通过 TypeScript,你在处理 result.data 之前必须先判断 result.success,减少了空指针崩溃。
  2. 错误分类:将“未登录”、“无权限”和“数据库崩溃”明确区分开。
  3. 多环境复用:同一套 DAL 代码可以无缝用于页面渲染、后台接口和自动化脚本,因为它的逻辑是“纯粹”的返回,不依赖于 Next.js 的路由环境。

这种模式虽然增加了一些样板代码(每个函数都要返回对象),但在中大型项目中,它带来的可测试性安全性远超基础模式。

 

这不就是我一直寻找的问题答案吗?我之前一直担心nextjs里面的数据操作,怎么处理错误?要不要写try...catch?写了try...catch之后,boilerplate代码太多了,显示不出我的能力啊。这个dal-advanced不就解决了问题嘛。

多看几遍,一遍肯定是看不懂的。看懂了之后,自己写几遍。

 

dal-advanced 的 helper里面的函数分析

这个文件里面是一套封装函数,这段代码本质上是一套工具集,目的是让你的 Next.js 应用在处理数据库请求时,既安全又灵活。

核心原理是:将数据操作变成一个可以被层层拦截、层层处理的“管道”

我们可以把这些函数拆分为 执行者 - 处理数据库(dalDbOperation)、动作适配器 - 处理结果(Redirect/Throw/VerifySuccess)、守门员/保镖(RequireAuth)和辅助工具(Format)。

第一类:执行者(处理原始数据库操作)

  1. dalDbOperation<T>(operation)

这是所有数据库操作的“防护壳”。

第二类:保镖(权限拦截)

  1. dalRequireAuth<T>(operation, options)

这是最核心的函数,用于定义一个受保护的数据操作。

第三类:动作适配器(在页面/Action 中消费结果)

这组函数决定了当 DAL 返回结果后,程序该表现出什么行为

  1. dalLoginRedirect(dalReturn)

  1. dalUnauthorizedRedirect(dalReturn, path)

  1. dalThrowError(dalReturn)

  1. dalVerifySuccess(dalReturn, options) —— 终极聚合函数

这是你在 Server Component(页面文件)里最常用的函数。

这是一个链式组合。它依次检查:是否没登录?(是就跳登录页) -> 是否没权限?(是就跳首页或其它页) -> 是否有其他错误?(是就直接抛异常)。

 

第四类:辅助工具

  1. dalFormatErrorMessage(error)

总结:这些函数是如何串联工作的?

为了帮你理清思路,请看下面这个逻辑流向图:

完整的使用流程如下:

  1. 定义层 (Data Layer)

    使用 dalRequireAuth 包装你的 SQL 查询,确保用户登录后才能操作SQL。使用dalDbOperation包裹数据库操作,使用try...catch捕获错误,将返回结果或者错误包装成对象返回。

  2. 处理层 (Action/Page)

    • 如果你在 Page 里,用 dalVerifySuccess:遇到错误自动重定向,成功直接拿数据。
    • 如果你在 Server Action 里,手动判断 result.success:如果失败,用 dalFormatErrorMessage 拿到文字发回给前端显示。

这样写的最大好处: 你的业务逻辑(SQL)只需要写一次,但在“直接跳页面”和“只显示报错文字”两个场景下都能复用。

  1. 逻辑流示意图

为了让你理解这些函数是怎么串联的,看这个流程:

UI 发起请求 调用一个 DAL 函数。

DAL 函数内部 调用 dalRequireAuth

dalRequireAuth 检查权限 失败则返回错误对象 成功则调用 dalDbOperation

dalDbOperation 执行 SQL 成功则返回 { success: true, data }

UI 拿到结果 调用 dalVerifySuccess

 

  1. 为什么这样写是“资深”?

这段代码解决了一个 Next.js 开发中的巨大难题:灵活性

总结

 

dal-advanced 怎么显示错误?

这是一个非常棒的问题,它触及了 dal-advanced 模式的核心价值:灵活性

dal-advanced 并不强制你报错的方式,它只是给了你一份“标准化的诊断报告”,由你决定是在大屏幕上显示(Error 页面),还是弹一个小窗口(Toast)。


  1. 触发 error.tsx (全屏报错)

如果你希望 Next.js 捕捉到错误并跳转到错误页面,你会使用 dalThrowErrordalVerifySuccess

只要一 throw,Next.js 就会自动激活最近的 error.tsx。

  1. 触发 Toast (轻量提醒)

如果你在 Server Action(比如用户点击“收藏”按钮)中使用,你通常不希望整个页面刷新报错,而是想在角落弹出一个 Toast。


  1. 触发 redirect (自动跳转)

这是 dal-advanced 提供的第三种路径。


  1. 总结对比

我们可以通过下面这张图来看清错误流转的过程:

处理方式使用的函数最终 UI 表现适用场景
硬拦截dalVerifySuccesserror.tsx 页面必须有数据才能渲染的页面组件。
软处理dalFormatErrorMessageToast 或 表单文字按钮点击、表单提交等异步交互。
重定向dalLoginRedirect浏览器 URL 变化,跳转页面鉴权失败、权限不足。
  1. 为什么 Kyle 觉得这很“高级”?

因为在没有这个系统之前,如果你在数据库函数里写死了 throw new Error(),你就只能用 error.tsx 报错,没法轻松地改成用 Toast 提示。

有了 dal-advanced,数据库函数只管吐出结果,显示方式的选择权交给了调用它的那行代码。

 

 

一些动作适配器在定义请求方法的时候已经使用了(我的感觉是操作已经被保护好了),为什么页面中真正请求数据的时候,还要加上dalVerifySuccess呢?不重复吗

比如说这段代码:

已经使用dalDbOperationdalRequireAuth保护好了操作,如果有报错,那么就会返回error数据,为什么还需要使用dalVerifySuccess来检查一遍呢?我的感觉就是代码重复了。

image-20260428150253609

 

 

简单回答:因为如果报错了,dalDbOperationdalRequireAuth只能返回错误对象,没有办法对错误进行UI方面的处理。而dalVerifySuccess可以依据相应的错误类型,对错误进行UI上的处理,比如说返回login页面、或者给出toast提示并返回上一页等等。

以下是Gemini给出的回答:

这是一个非常敏锐的观察!初看之下,确实感觉“保镖”和“医生”做了两遍检查。

但实际上,这正是 dal-advanced 模式最精妙的地方:一个是逻辑拦截,一个是UI决策。 它们并不重复,而是互补的。

以下是深度拆解,告诉你为什么这两行代码缺一不可:


  1. dalRequireAuth(在 getAllTodos 内部)

角色:底线保镖

  1. 组件中的 if (res.error.type === "no-access")

角色:定制化 UI 策略

  1. dalVerifySuccess

角色:兜底清理员

  1. 为什么这不叫“重复”?

请看这个逻辑分流过程

如果你删掉其中一个会发生什么?


  1. 总结:资深开发者的思维链条
  1. 查询层(DAL):总是用 dalRequireAuth 封装,保证安全性

  2. 页面层(Page)

    • 我有特殊的 UI 需求吗? 有(比如想显示特定文字),那就手动 if 拦截掉。
    • 剩下的按默认规矩办:直接丢给 dalVerifySuccess。它会帮我把“没登录”的用户送走,把“报错”的用户交给错误页,最后顺手把干净的数据解构出来给我用。

作者在这里展示的是:如何利用 dal-advanced 模式,既保留了默认的重定向/报错功能,又能灵活地为特定错误提供自定义的 UI 渲染。

 

How to handle Errors Like a Senior 视频解析

https://www.youtube.com/watch?v=ovnyeq-Xxrc

视频中 Kyle (Web Dev Simplified) 按顺序演示了 5 个递进的错误处理版本,核心场景是 Next.js 项目(server actions / API routes)中处理登录、权限等错误(如 Unauthenticated、Unauthorized、ValidationError、Unexpected 等)。他使用了自定义错误类、权限检查、redirect vs JSON 响应等典型示例。

  1. Starting Code(00:54 - 02:21):初级/散乱方式(直接在 Action/Route 中处理)

直接在 server action 或 API route 里写业务逻辑 + try-catch,处理不同错误类型(redirect 或 return error message)。

典型代码(重建自演示风格)

哪里好

哪里不好

 

  1. Service Layer(04:37 - 13:49):引入服务层,重构业务逻辑

把核心业务 + 权限检查抽到独立 service 文件,service 只负责返回数据或 throw custom errors,不处理 redirect/JSON。

典型代码

哪里好

哪里不好

 

  1. More Problems(13:49 - 15:44):服务层后的剩余问题

Kyle 特别强调即使用了 service layer,问题依然存在:

好/不好:基本同上,只是更清晰地展示了为什么需要下一步。

 

  1. Simple Solution(15:44 - 26:02):自定义 Result 类型(discriminated union)

不再 throw,而是返回 Result 类型(success 或 failure),用 discriminated union 让 TS 自动 narrowing。

典型代码(视频中使用 tuple 风格,简化版)

哪里好

哪里不好

 

  1. Library Solution(26:02 - end):Neverthrow 库

引入 neverthrow(轻量,无依赖),提供 Ok/Err、match、andThen、map 等方法,支持 AsyncResult。

典型代码

还提到 ESLint 插件强制使用 Result(禁止直接 throw 或忽略错误)。

哪里好

哪里不好

总体:Kyle 的讲解非常清晰,从问题到方案递进强烈,推荐中高级 TS/Next.js 开发者观看。核心理念是“错误作为显式值 + 类型安全 + 架构分离”。没有专用 repo,代码是演示性质的。

 

 

服务端请求怎么调试

在 Next.js 的 App Router(Server Components) 中,服务端发出的 fetch 请求不会出现在浏览器的 Network 面板里,这是目前(2025年底)框架的设计使然。但还是可以手动输出一些日志来调试。

1、在 next.config.js 里打开 fetch 日志(Next.js 内置,最方便)

2、加上基本的错误捕获 + 丰富日志

你会看到类似这样清晰的输出: